package org.example.domain.transaction.service;


import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.example.domain.transaction.adapter.repository.ITransactionRepository;
import org.example.domain.transaction.model.entity.PayCallbackEntity;
import org.example.domain.transaction.model.entity.RefundCallBackEntity;
import org.example.domain.transaction.model.valobj.PayStatusEnumVO;
import org.example.types.enums.ResponseCode;
import org.example.types.exception.AppException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import static org.example.types.common.MQConstants.*;
import static org.example.types.common.RedisConstants.PAY_CALLBACK_LOCK_PREFIX;
import static org.example.types.common.RedisConstants.REFUND_CALLBACK_LOCK_PREFIX;
import static org.example.types.enums.ResponseCode.*;

/**
 * @description:  
 * @author yinghuashen
 * @date 2025/3/14 10:26
 * @version 1.0
 */

@Slf4j
@Service
public class TransactionService implements ITransactionService{

    @Resource
    private ITransactionRepository transactionRepository;

    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void payCallBack(PayCallbackEntity payCallbackEntity) {
        Boolean isLocked = stringRedisTemplate.opsForValue().
                setIfAbsent(PAY_CALLBACK_LOCK_PREFIX + payCallbackEntity.getOrderId(),
                        Thread.currentThread().getName());

        if(isLocked == null || !isLocked){
            log.error("取锁失败, 同一订单支付回调正在进行中");
            throw new AppException(ResponseCode.TRANSACTION_DUPLICATE_PAYMENT_ERROR);
        }

        try {
            // 1. 幂等性检查：检查该外部单是否已记录
            boolean outRecordExisted = transactionRepository.checkPayCallbackResultExisted(payCallbackEntity.getOutTradeNo());
            if (outRecordExisted) {
                log.error("支付回调重复");
                throw new AppException(ResponseCode.TRANSACTION_OUTRECORD_DUPLICATION_ERROR);
            }
            // 2. 幂等性检查：检查对应订单是否存在
            boolean inOrderExisted = transactionRepository.checkInOrderExisted(payCallbackEntity.getOrderId());
            if (!inOrderExisted) {
                log.error("支付回调内部订单缺失");
                throw new AppException(ResponseCode.TRANSACTION_INORDER_MISSED_ERROR);
            }
            // 3. 幂等性检查：检查对应订单是否已支付
            boolean inOrderIsPendingPayment = transactionRepository.checkInOrderIsPendingPayment(payCallbackEntity.getOrderId());
            if (!inOrderIsPendingPayment) {
                log.error("对同一订单发起重复");
                throw new AppException(ResponseCode.TRANSACTION_DUPLICATE_PAYMENT_ERROR);
            }

            // 4. 记录支付结果
            try {
                transactionRepository.savePayCallbackResult(payCallbackEntity);
            } catch (Exception e) {
                throw new AppException(TRANSACTION_OUTRECORD_INSERT_ERROR);
            }

            // 5. 处理支付结果
            if(payCallbackEntity.getStatus() != PayStatusEnumVO.SUCCESS.getCode()){
                return;
            }
            // 5.1. 支付成功，发送支付成功消息
            Message<PayCallbackEntity> message = MessageBuilder.withPayload(payCallbackEntity).build();

            SendResult sendResult = rocketMQTemplate.syncSend(SUCCEED_PAY_CALLBACK_TOPIC, message);
            if(sendResult.getSendStatus() != SendStatus.SEND_OK){
                // 插入本地信息表
                transactionRepository.saveSucceedPayCallbackMsg(payCallbackEntity);
            }
        } finally {
            // 释放锁
            String threadName = Thread.currentThread().getName();
            if(threadName.equals(stringRedisTemplate.opsForValue().get(PAY_CALLBACK_LOCK_PREFIX + payCallbackEntity.getOrderId()))){
                stringRedisTemplate.delete(PAY_CALLBACK_LOCK_PREFIX + payCallbackEntity.getOrderId());
            }
        }
    }

    @Override
    public void refundCallBack(RefundCallBackEntity refundCallBackEntity) {
        // 加锁，同一时刻只有一个订单的退款申请可以被受理
        Boolean isLocked = stringRedisTemplate.opsForValue().
                setIfAbsent(REFUND_CALLBACK_LOCK_PREFIX + refundCallBackEntity.getOrderId(),
                        Thread.currentThread().getName());

        if(isLocked == null || !isLocked){
            log.error("取锁失败, 同一订单退款回调正在进行中");
            throw new AppException(TRANSACTION_REFUND_DUPLICATION_ERROR);
        }

        try {
            // 1. 幂等性检验：外部交易单表中该交易单是已支付的 (才可以退款)
            boolean isPaid = transactionRepository.checkPaid(refundCallBackEntity.getOutTradeNo());
            if(!isPaid){
                log.error("该外部交易单状态异常, outTradeNo: {}", refundCallBackEntity.getOutTradeNo());
                throw new AppException(TRANSACTION_REFUND_OUTRECORD_UNPAID_ERROR);
            }

            // 2. 开启RocketMQ事务消息
            Message<RefundCallBackEntity> message = MessageBuilder.withPayload(refundCallBackEntity).build();

            TransactionSendResult transactionSendResult =
                    rocketMQTemplate.sendMessageInTransaction(REFUND_PRODUCER_GROUP, REFUND_TOPIC, message, refundCallBackEntity);
            if (transactionSendResult.getSendStatus() != SendStatus.SEND_OK || transactionSendResult.getLocalTransactionState()!= LocalTransactionState.COMMIT_MESSAGE) {
                log.error("发送退款事务消息失败，outTradeNo: {}", refundCallBackEntity.getOutTradeNo());
                throw new AppException(TRANSACTION_REFUND_UPDATE_ERROR);
                // TODO 回调外部，退款失败
            }
        } finally {
            // 释放锁
            String threadName = Thread.currentThread().getName();
            if(threadName.equals(stringRedisTemplate.opsForValue().get(REFUND_CALLBACK_LOCK_PREFIX + refundCallBackEntity.getOrderId()))){
                stringRedisTemplate.delete(REFUND_CALLBACK_LOCK_PREFIX + refundCallBackEntity.getOrderId());
            }
        }
    }
}
